/*
 *  drivergate.c
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/mutex.h>
#include "drivergate.h"

MODULE_LICENSE("GPL");

/*
 * Create a kernel mutex to protect the gate status.
 * This is necessary because we are simulating a switch being
 * flipped instead of physically flipping a switch, therefore
 * our driver needs to be opened by 2 processes at once.
 * One to poll the status and one to change the status,
 * therefore synchronisation is necessary.
 */
DEFINE_MUTEX(statusMutex); 

static int Gate_Status = 0;

static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
	printk(KERN_INFO "device_open(%p)\n", file);
#endif

	try_module_get(THIS_MODULE);
	return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
	printk(KERN_INFO "device_release(%p,%p)\n", inode, file);
#endif

	module_put(THIS_MODULE);
	return SUCCESS;
}

int device_ioctl(struct inode *inode,
		 struct file *file,
		 unsigned int ioctl_num,
		 unsigned long ioctl_param)
{
/*
 * Synchronised section.
 */
	mutex_lock(&statusMutex);
	switch (ioctl_num) {
	case GATE_OPEN:
		Gate_Status = 1;
		break;
	case GATE_CLOSE:
		Gate_Status = 0;
		break;
	case GATE_STATUS:
		put_user(Gate_Status, (int *)ioctl_param);
		break;
	default:
		/*
		 * Default case to return an error just in case.
		 */
		mutex_unlock(&statusMutex);
		return -1;
	}

	mutex_unlock(&statusMutex);
	return SUCCESS;
}

/*
 * Operations supported by the gate device.
 * Read and write not supported because using ioctl to change and return the 
 * gate status.
 */
struct file_operations Fops = {
	.ioctl = device_ioctl,
	.open = device_open,
	.release = device_release,
};

/* 
 * Register the gate device. 
 */
int init_module()
{
	int ret_val;
	ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

	if (ret_val < 0) {
		printk(KERN_ALERT "Registering gate device failed with %d\n", ret_val);
		return ret_val;
	}

	return 0;
}

/* 
 * Cleanup - unregister the appropriate file from /proc 
 */
void cleanup_module()
{
	/* 
	 * Unregister the device 
	 */
	unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}

